package com.jzo2o.orders.manager.handler;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jzo2o.api.trade.RefundRecordApi;
import com.jzo2o.api.trade.dto.response.ExecutionResultResDTO;
import com.jzo2o.common.constants.UserType;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.orders.base.enums.OrderRefundStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.domain.OrdersRefund;
import com.jzo2o.orders.manager.model.dto.OrderCancelDTO;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.IOrdersManagerService;
import com.jzo2o.orders.manager.service.IOrdersRefundService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

/**
 * 专门用来处理定时任务
 * @author Mr.M
 * @version 1.0
 * @description 订单定时任务
 * @date 2023/12/15 10:14
 */
@Component
@Slf4j
public class OrdersHandler {

    @Resource
    private IOrdersCreateService ordersCreateService;

    @Resource
    private IOrdersManagerService ordersManagerService;
    
    @Resource
    private IOrdersRefundService ordersRefundService;

    @Resource
    private RefundRecordApi refundRecordApi;

    @Resource
    private OrdersMapper ordersMapper;
    

    /**
     * 自动取消支付超时的订单
     * 支付超时：从下单开始15分钟还没有支持
     */
    @XxlJob("cancelOverTimePayOrder")
    public void cancelOverTimePayOrder() {

        //查询出支付超时的订单
        List<Orders> ordersList = ordersCreateService.queryOverTimePayOrdersListByCount(100);
        if(CollUtils.isEmpty(ordersList)){  //这个工具类CollUtils是专门用来处理List的工具类,好像是老师定义的,不是框架自己的
            log.info("自动取消支付超时的订单，未查询到订单信息");
            return ;
        }

        //遍历，调用订单取消方法
        for (Orders orders : ordersList) {
            OrderCancelDTO orderCancelDTO = BeanUtils.toBean(orders,OrderCancelDTO.class);
            //是系统本身来执行的操作，不是c端用户;  所以下面就不能再用threadLocal获取了用户了
            orderCancelDTO.setCurrentUserType(UserType.SYSTEM);  //这里专门指定了,这个用户类型,这里是"系统"操作的
            //订单取消原因,,原来是用户选择,然后前端传过来; 这个定时任务就直接写了
            orderCancelDTO.setCancelReason("订单超时支付，自动取消");  
            ordersManagerService.cancel(orderCancelDTO);
        }

    }

    /**
     * 退款定时任务
     * 比如配送中的状态,,然后退款
     */
    @XxlJob("handleRefundOrders")
    public void handleRefundOrders(){

        //下面的三步,,都单独定义了方法,直接调用的
        
        //查询退款记录表
        List<OrdersRefund> ordersRefunds = ordersRefundService.queryRefundOrderListByCount(100);
        if(CollUtils.isEmpty(ordersRefunds)){
            log.info("退款定时任务，未查询到订单的退款记录");
            return ;
        }
        //遍历退款记录，请求支付服务进行退款
        for (OrdersRefund ordersRefund : ordersRefunds) {
            //请求支付服务进行退款
            requestRefundOrder(ordersRefund);
        }

        //如果退款成功更新订单的退款状态
    }

    /**
     * 请求退款
     * 单独定义的一个方法,上面handleRefundOrders会调用到
     * 这个就叫单一原则,,就是一个方法里面不能有太多功能;要不也不利于复用
     * @param ordersRefund 退款记录
     */
    public void requestRefundOrder(OrdersRefund ordersRefund){
        ExecutionResultResDTO executionResultResDTO = null;
        //请求支付服务进行退款
            //这里就要远程调用支付服务了;并且这里的逻辑是全额退款就是你实付多少就退多少,,老师说有的业务可以分批退,,就是一次退一点儿
        
        //因为支付接口退款失败就会抛异常,,所以这里也要捕获到
        try {
            //这里单独抛出来异常,,,就是请求支付接口退款是批量退款,如果退款失败,它就会抛出来异常, 然后这里捕获
                //但是上面请求退款,是遍历退款的,,如果你抛出来异常,那退款的定时任务方法就直接结束了,那后续订单的退款就没法运行了,
                // 所以我们希望,这一个订单退款失败,但是不要影响到下面的退款,,就是想让他继续遍历退款别的订单
                // 这里单独异常的代码如果不理解,,可以看看视频
             executionResultResDTO = refundRecordApi.refundTrading(ordersRefund.getTradingOrderNo(), ordersRefund.getRealPayAmount());
        } catch (Exception e) {
           e.printStackTrace();  //这里打个日志
            //原本这里的代码应该是:   throw new RuntimeException,所以改成上面只输出个日志,而不抛异常,,这样子方法就不会结束;
                //这个想法真好,,看来自己对异常学习的也很懵懂;try_catch已经捕获了异常,但是我们只输出日志,而没有抛出异常,这样下面方法就能继续执行
        }
        //解析退款结果，如果不是退款中(退款成功/退款失败状态下)  都要更新订单的退款状态
        if(ObjectUtils.isNotNull(executionResultResDTO) && executionResultResDTO.getRefundStatus()!= OrderRefundStatusEnum.REFUNDING.getStatus()){
            //更新订单的退款状态
                //因为别的业务可能也要用这个方法,,所以下面又单独定义了这个refundOrder方法
            refundOrder(ordersRefund,executionResultResDTO);
        }
    }

    //更新订单的退款状态，只更新要么是退款成功要么是退款失败,就是这两个状态,要把订单表的状态更新下
    public void refundOrder(OrdersRefund ordersRefund,ExecutionResultResDTO executionResultResDTO){

        //初始的退款状态为退款中;;就是一个初始值,,没啥别的逻辑,比如int num = 0;
        Integer refundStatus = OrderRefundStatusEnum.REFUNDING.getStatus();
        //如果支付服务返回的退款状态,,就是退款成功,那我们就把它
        if(executionResultResDTO.getRefundStatus()==OrderRefundStatusEnum.REFUND_SUCCESS.getStatus()){
            //把对应的状态,赋值给refundStatus
            refundStatus = OrderRefundStatusEnum.REFUND_SUCCESS.getStatus();
        }else if(executionResultResDTO.getRefundStatus()==OrderRefundStatusEnum.REFUND_FAIL.getStatus()){//如果支付服务 返回退款失败
            refundStatus = OrderRefundStatusEnum.REFUND_FAIL.getStatus();
        }
        //老师这里思路牛的点在于,,如果返回的状态既不是退款成功,也不是退款失败,那应该是退款中状态;这就体现出老师最开始定义初始状态为"退款中"的意义了
            //如果真的是"退款中",那就不会执行if语句中的操作
        
        //如果是退款中状态，程序结束
        if (ObjectUtil.equal(refundStatus, OrderRefundStatusEnum.REFUNDING.getStatus())) {
            return;
        }
        
        //下面才是更新数据库的主要代码,,前面都是判断的逻辑 
        //更新订单表的"退款状态、支付服务的退款单号、第三方支付平台的退款单号"
            //下面这个sql状态,,有点不好理解,别别扭扭的; 我感觉,如果是退款成功,我在更新成退款成功状态,,好像的确不合适,不过这里也不是退款,只是更新订单状态,感觉也没影响呀
                //我问文心一言,,我那种想法还是不好;这种设计模式通常用于确保状态不会被重复设置，从而避免不必要的数据库操作或潜在的逻辑错误。
        //sql:update orders set refund_status=退款成功 where id=? and refund_status!=退款成功
        //sql:update orders set refund_status=退款失败 where id=? and refund_status!=退款失败

        LambdaUpdateWrapper<Orders> updateWrapper = new LambdaUpdateWrapper<Orders>()
                .eq(Orders::getId, ordersRefund.getId())//订单id
                .ne(Orders::getRefundStatus, refundStatus)//拼接  and refund_status!=退款成功  或 and refund_status!=退款失败
                //相当于上面这个.ne拼接的是不等于号,,即getRefundStatus!= refundStatus
                .set(Orders::getRefundStatus, refundStatus)  //更新退款状态
                .set(Orders::getRefundId, executionResultResDTO.getRefundId())  //第三方支付的退款单号
                .set(Orders::getRefundNo, executionResultResDTO.getRefundNo());  //支付服务退款单号
        
        //更新订单表
        int update = ordersMapper.update(null,updateWrapper); 
        //因为这个update方法要传入一个实体类,我们只让根据updateWrapper条件更新就可以,所以前面传了个null 
        
        if(update>0){ //表示更新成功
            //清除退款记录
            ordersRefundService.removeById(ordersRefund.getId());
        }

    }

    /**
     *  新启动一个线程请求支付服务进行退款
     *  这个用于及时退款,,前面的定时退款,实际生产环境中,不可能5秒一查,太耗费资源了
     * @param ordersRefundId 订单id
     */
    public void requestRefundNewThread(Long ordersRefundId){
        //新启一个线程
        new Thread(()->{
            //查询退款记录表(因为更新状态的时候,都会更新这个记录表,当退款完成的时候,退款定时任务兜底会删除这个退款记录表,,所以不用再去orders订单表再封装OrdersRefund对象了)
            OrdersRefund ordersRefund = ordersRefundService.getById(ordersRefundId);
            if(ObjectUtils.isNotNull(ordersRefund)){
                //请求支付服务进行退款
                requestRefundOrder(ordersRefund);
                //上面这个方法,,在定时任务取消退款的时候定义过了,,这里就体现出每个功能写单独代码块的好处,,可以复用; 这里都是退款的逻辑
            }
        }).start();
    }




}
